package lelouet.datacenter;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import lelouet.datamap.DataMap;
import lelouet.datamap.DataMapFactory;
import lelouet.datamap.ServerPoint;

/**
 * Mapping of a set of names to indexes. This mapping is unmodifiable, and made
 * at creation.<br />
 * this allows conversion of double[] to {@link DataMap} , and {@link DataMap}
 * to double[] according to the indices : the {@link DataMap} is used for
 * storage/presentation, the double[] for faster manipulation via matrix/vector
 * operations<br />
 */
public class ArrayModel {
	/**
	 * the mapping between the servers and the indexes they are located.<br />
	 * Is constructed unmutable so it can easily be returned. Since
	 * {@link String} and {@link Integer} are unmutable, this is a from-creation
	 * constant map.
	 */
	private final Map<String, Integer> coding;

	/**
	 * constructs a new {@link ArrayModel} using the same indexed elements that
	 * another one uses.
	 * 
	 * @param other
	 *            another {@link ArrayModel} from which we get the indexed
	 *            elements
	 */
	protected ArrayModel(ArrayModel other) {
		coding = other.coding;
	}

	/**
	 * constructs an {@link ArrayModel} using a given set of Objects<br />
	 * 
	 * @param toIndex
	 *            the collection of {@link Object} used. it is copied so further
	 *            modification will have no impact.
	 */
	public ArrayModel(Collection<? extends Object> toIndex) {
		HashMap<String, Integer> coding = new HashMap<String, Integer>();
		for (Object m : toIndex) {
			coding.put(m.toString(), coding.size());
		}
		this.coding = Collections.unmodifiableMap(coding);
	}

	/**
	 * @return an immutable {@link HashMap} of used names to their indexes
	 */
	public Map<String, Integer> getCoding() {
		return coding;
	}

	/** @return an immutable {@link Set} of used names */
	public Set<String> getNames() {
		return coding.keySet();
	}

	/** @return the index stored for given name, if it exists */
	public Integer getIndex(String name) {
		return coding.get(name);
	}

	/** @return the number of name known. */
	public int size() {
		return coding.size();
	}

	/**
	 * generates the array of values associated to server in a datamap
	 * 
	 * @return an array of each last value associated to server i at index i.
	 */
	public double[] convertMap(DataMap map) {
		double[] ret = new double[size()];
		for (ServerPoint point : map.getServerPoints()) {
			String modifiedName = point.getPosition() == null ? point
					.getServer() : point.getServer() + '.'
					+ point.getPosition();
			Integer index = getIndex(modifiedName);
			if (index != null) {
				ret[index] = point.getValue();
			}
		}
		return ret;
	}

	/**
	 * copy all correct data
	 * 
	 * @throws ArrayIndexOutOfBoundsException
	 *             if the source is not large enough
	 */
	public DataMap convertArray(double[] source) {
		DataMapFactory factory = new DataMapFactory();
		DataMap ret = factory.createDataMap();
		for (Entry<String, Integer> e : getCoding().entrySet()) {
			if (source.length > e.getValue()) {
				if (source[e.getValue()] != 0) {
					ret.getServerPoints().add(
							factory.createServerPoint(e.getKey(), source[e
									.getValue()]));
				}
			} else {
				throw new ArrayIndexOutOfBoundsException(
						"error : the source is not wide enough and does not contain the server "
								+ e.getKey());
			}
		}
		return ret;
	}

}
